{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "436ada0d",
   "metadata": {},
   "source": [
    "In this notebook I want to illustrate how one can use our `[...]FromFolder` functionalities along with the `[...]ChunkPreProcessors` in those cases where the dataset is too bit to fit in memory.\n",
    "\n",
    "These functionalities in the library have been designed for the following scenarop\n",
    "\n",
    "### Scenario\n",
    "\n",
    "We have a tabular dataset combined with images and text and either some, or all these datasets do not fit in memory. Note that the tabular dataset **MUST ALWAYS** be present as it is considered the rerefence. This is, if we have an image dataset, the tabular dataset must contain a column that points to the image file names as stored in disk. Similarly, if we have a text dataset, then the tabular dataset must contain a column with the texts themselves or a the file names of the text files as stored in disk. \n",
    "\n",
    "If you only have text and/or images and not a tabular component, I would suggest using other libraries (such as hugginface probably). \n",
    "\n",
    "Within this scenario, they are two possible scenarios that we will cover here: \n",
    "\n",
    "1. The tabular data itsel fits in memory and is only the images that do not: in this case you could use the '*standard*' `Preprocessors` in the library and off you go, move directly to the `[...]FromFolder` functionalities\n",
    "\n",
    "2. The tabular data is also very large and does not fit in memory, so we have to process it in chuncks. For this second case I have created the so called `Chunk[...]Preprocessor` (Wide, Tab and Text). \n",
    "\n",
    "Note that at the moment **ONLY csv** format is allowed for the tabular file. More formats will be supported in the future.\n",
    "\n",
    "Let's see a complete example to illustrate how each of these cases would be addressed with the new functionalities in the library. For this example we will use a sample of the airbnb dataset\n",
    "\n",
    "The airbnb dataset, which you could get from [here](http://insideairbnb.com/get-the-data.html), is too big to be included in our datasets module (when including images). Therefore, what I did was, go there, download it, and use the download_images.py script to get the images and the airbnb_data_processing.py to process the data. I did this ages ago and I believe the format of the dataset might be different now. Nonetheless, I will show samples of the dataset as we go through so you can extrapolate the content of this notebook to your particular problem. \n",
    "\n",
    "In the future we will find better datasets🙂. Finally, note that here we are only using a small sample to illustrate the use, so PLEASE ignore the results, just focus on usage. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "aa3e9949",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import torch\n",
    "import pandas as pd\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "from pytorch_widedeep.models import TabMlp, Vision, BasicRNN, WideDeep\n",
    "from pytorch_widedeep.training import TrainerFromFolder\n",
    "from pytorch_widedeep.callbacks import EarlyStopping, ModelCheckpoint\n",
    "from pytorch_widedeep.preprocessing import (\n",
    "    TabPreprocessor,\n",
    "    TextPreprocessor,\n",
    "    ImagePreprocessor,\n",
    "    ChunkTabPreprocessor,\n",
    "    ChunkTextPreprocessor,\n",
    ")\n",
    "from pytorch_widedeep.load_from_folder import (\n",
    "    TabFromFolder,\n",
    "    TextFromFolder,\n",
    "    ImageFromFolder,\n",
    "    WideDeepDatasetFromFolder,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "55341e9e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "      <th>host_id</th>\n",
       "      <th>description</th>\n",
       "      <th>host_listings_count</th>\n",
       "      <th>host_identity_verified</th>\n",
       "      <th>neighbourhood_cleansed</th>\n",
       "      <th>latitude</th>\n",
       "      <th>longitude</th>\n",
       "      <th>is_location_exact</th>\n",
       "      <th>property_type</th>\n",
       "      <th>...</th>\n",
       "      <th>amenity_wide_entrance</th>\n",
       "      <th>amenity_wide_entrance_for_guests</th>\n",
       "      <th>amenity_wide_entryway</th>\n",
       "      <th>amenity_wide_hallways</th>\n",
       "      <th>amenity_wifi</th>\n",
       "      <th>amenity_window_guards</th>\n",
       "      <th>amenity_wine_cooler</th>\n",
       "      <th>security_deposit</th>\n",
       "      <th>extra_people</th>\n",
       "      <th>yield</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>39</th>\n",
       "      <td>53242.jpg</td>\n",
       "      <td>247650</td>\n",
       "      <td>A lovely big bright bedroom in a 2 bedroom fla...</td>\n",
       "      <td>2.0</td>\n",
       "      <td>t</td>\n",
       "      <td>Lambeth</td>\n",
       "      <td>51.47075</td>\n",
       "      <td>-0.12913</td>\n",
       "      <td>t</td>\n",
       "      <td>apartment</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>250.0</td>\n",
       "      <td>5.0</td>\n",
       "      <td>9.75</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>214</th>\n",
       "      <td>236716.jpg</td>\n",
       "      <td>1241070</td>\n",
       "      <td>We offer a warm welcome in our quiet double ro...</td>\n",
       "      <td>1.0</td>\n",
       "      <td>t</td>\n",
       "      <td>Hackney</td>\n",
       "      <td>51.56593</td>\n",
       "      <td>-0.07482</td>\n",
       "      <td>t</td>\n",
       "      <td>other</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>200.0</td>\n",
       "      <td>10.0</td>\n",
       "      <td>76.50</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>400</th>\n",
       "      <td>346523.jpg</td>\n",
       "      <td>1756532</td>\n",
       "      <td>Available for you to rent is a cozy studio in ...</td>\n",
       "      <td>2.0</td>\n",
       "      <td>t</td>\n",
       "      <td>Kensington and Chelsea</td>\n",
       "      <td>51.48311</td>\n",
       "      <td>-0.18428</td>\n",
       "      <td>t</td>\n",
       "      <td>other</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>50.0</td>\n",
       "      <td>180.90</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>512</th>\n",
       "      <td>389627.jpg</td>\n",
       "      <td>1949299</td>\n",
       "      <td>This gorgeous studio flat is situated in the v...</td>\n",
       "      <td>1.0</td>\n",
       "      <td>t</td>\n",
       "      <td>Westminster</td>\n",
       "      <td>51.51838</td>\n",
       "      <td>-0.14238</td>\n",
       "      <td>f</td>\n",
       "      <td>apartment</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>250.0</td>\n",
       "      <td>25.0</td>\n",
       "      <td>276.90</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>504</th>\n",
       "      <td>388767.jpg</td>\n",
       "      <td>1945165</td>\n",
       "      <td>If you want to experience London at it's best ...</td>\n",
       "      <td>2.0</td>\n",
       "      <td>f</td>\n",
       "      <td>Camden</td>\n",
       "      <td>51.54293</td>\n",
       "      <td>-0.14073</td>\n",
       "      <td>t</td>\n",
       "      <td>apartment</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>150.0</td>\n",
       "      <td>10.0</td>\n",
       "      <td>591.10</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 223 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "             id  host_id                                        description  \\\n",
       "39    53242.jpg   247650  A lovely big bright bedroom in a 2 bedroom fla...   \n",
       "214  236716.jpg  1241070  We offer a warm welcome in our quiet double ro...   \n",
       "400  346523.jpg  1756532  Available for you to rent is a cozy studio in ...   \n",
       "512  389627.jpg  1949299  This gorgeous studio flat is situated in the v...   \n",
       "504  388767.jpg  1945165  If you want to experience London at it's best ...   \n",
       "\n",
       "     host_listings_count host_identity_verified  neighbourhood_cleansed  \\\n",
       "39                   2.0                      t                 Lambeth   \n",
       "214                  1.0                      t                 Hackney   \n",
       "400                  2.0                      t  Kensington and Chelsea   \n",
       "512                  1.0                      t             Westminster   \n",
       "504                  2.0                      f                  Camden   \n",
       "\n",
       "     latitude  longitude is_location_exact property_type  ...  \\\n",
       "39   51.47075   -0.12913                 t     apartment  ...   \n",
       "214  51.56593   -0.07482                 t         other  ...   \n",
       "400  51.48311   -0.18428                 t         other  ...   \n",
       "512  51.51838   -0.14238                 f     apartment  ...   \n",
       "504  51.54293   -0.14073                 t     apartment  ...   \n",
       "\n",
       "    amenity_wide_entrance  amenity_wide_entrance_for_guests  \\\n",
       "39                      0                                 0   \n",
       "214                     0                                 0   \n",
       "400                     0                                 0   \n",
       "512                     0                                 0   \n",
       "504                     0                                 0   \n",
       "\n",
       "     amenity_wide_entryway  amenity_wide_hallways  amenity_wifi  \\\n",
       "39                       0                      0             1   \n",
       "214                      0                      0             1   \n",
       "400                      0                      0             1   \n",
       "512                      0                      0             1   \n",
       "504                      0                      0             1   \n",
       "\n",
       "     amenity_window_guards  amenity_wine_cooler security_deposit extra_people  \\\n",
       "39                       0                    0            250.0          5.0   \n",
       "214                      0                    0            200.0         10.0   \n",
       "400                      0                    0              0.0         50.0   \n",
       "512                      0                    0            250.0         25.0   \n",
       "504                      0                    0            150.0         10.0   \n",
       "\n",
       "      yield  \n",
       "39     9.75  \n",
       "214   76.50  \n",
       "400  180.90  \n",
       "512  276.90  \n",
       "504  591.10  \n",
       "\n",
       "[5 rows x 223 columns]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# in my case, I place the data in a folder I call tmp_data, let's see how it looks\n",
    "airbnb_data = pd.read_csv(\"../tmp_data/airbnb/airbnb_sample.csv\")\n",
    "airbnb_data.sample(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cd48be3d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# for example\n",
    "from IPython.display import Image\n",
    "\n",
    "pil_img = Image(filename=\"../tmp_data/airbnb/property_picture/272908.jpg\")\n",
    "display(pil_img)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ee08f9a2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[\"Bright, sunny beautiful room that will give you the perfect base to explore all of London. Come and explore one of London's best neighbourhoods - Herne Hill! As mentioned in (Website hidden by Airbnb)   (Website hidden by Airbnb)  WiFi availability with a fully stocked and clean uplifting home. Lovely sunny, airy and big double bedroom on a leafy south-London street.    Note: This room comes with a reserved Off-Street parking spot! The room is on the first floor and boasts an enormous Super King bed, gorgeous wooden floors, tall ceilings and large windows which let in the sunshine almost all day. (Yoga May or meditation cushion available on request) The flat is bright and airy and big! So lots of space for all.  Location wise you are only 10 minutes walk to either Herne Hill or West Dulwich stations, both of which will take you to Victoria and the city within minutes. You can also hop on a bus right outside the flat that will take you to Brixton tube station within 8 minutes where you \"]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# And the description for the property that that picture belongs to is:\n",
    "airbnb_data[airbnb_data.id == \"272908.jpg\"].description.tolist()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e78d3b6",
   "metadata": {},
   "source": [
    "Ok, so we have tabular data where one column is `description` and another `id`, points towards the images stored in disk. Now, remember the following, because this will appear a few times in the notebook: our \"reference dataset\" is the tabular data. \n",
    "\n",
    "Therefore, since I want to illustrate a \"semi-realistic\" case, if we need to split the data into training, validation and test datasets, these datasets needs to be separetely stored in disk. In my case I have done this and in the `tmp_data/airbnb` dir I have the following:\n",
    "\n",
    "```bash\n",
    "../tmp_data/airbnb\n",
    "├── airbnb_sample.csv\n",
    "├── airbnb_sample_eval.csv\n",
    "├── airbnb_sample_test.csv\n",
    "├── airbnb_sample_train.csv\n",
    "└── property_picture\n",
    "```\n",
    "\n",
    "Where `airbnb_sample.csv` is the full sample (1001 rows) and the `train`, `eval` and `test` set is the corresponding splits. In a realistic example, the full sample would be the '_gigantic_' dataset and the rest the corresponding splits. One has to do this 'offline', prior to start the coding. \n",
    "\n",
    "Also, one thing that one needs to know is the number of total observations/rows, as well as the splits. In our case the train size is 800, and the eval and test sizes are 100 and 101 respectively. \n",
    "\n",
    "With all that info, let's star"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb1d276a",
   "metadata": {},
   "source": [
    "## Setting variables and constants"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "36bd0e06",
   "metadata": {},
   "outputs": [],
   "source": [
    "# path to the tabular data and the splits\n",
    "data_path = \"../tmp_data/airbnb/\"\n",
    "train_fname = \"airbnb_sample_train.csv\"\n",
    "eval_fname = \"airbnb_sample_eval.csv\"\n",
    "test_fname = \"airbnb_sample_test.csv\"\n",
    "\n",
    "# split sizes\n",
    "train_size = 800\n",
    "eval_size = 100\n",
    "test_size = 101\n",
    "\n",
    "# number of chunks for the Chunk Preprocessors\n",
    "chunksize = 100\n",
    "n_chunks = int(np.ceil(train_size / chunksize))\n",
    "\n",
    "# path to the image dataset and name of the image col\n",
    "img_path = \"../tmp_data/airbnb/property_picture/\"\n",
    "img_col = \"id\"\n",
    "\n",
    "# name of the text col\n",
    "text_col = \"description\"\n",
    "\n",
    "# mane of the target\n",
    "target_col = \"yield\"\n",
    "\n",
    "# definition of the categorical and continuous cols for the TabPreprocessor\n",
    "cat_embed_cols = [\n",
    "    \"host_listings_count\",\n",
    "    \"neighbourhood_cleansed\",\n",
    "    \"is_location_exact\",\n",
    "    \"property_type\",\n",
    "    \"room_type\",\n",
    "    \"accommodates\",\n",
    "    \"bathrooms\",\n",
    "    \"bedrooms\",\n",
    "    \"beds\",\n",
    "    \"guests_included\",\n",
    "    \"minimum_nights\",\n",
    "    \"instant_bookable\",\n",
    "    \"cancellation_policy\",\n",
    "    \"has_house_rules\",\n",
    "    \"host_gender\",\n",
    "    \"accommodates_catg\",\n",
    "    \"guests_included_catg\",\n",
    "    \"minimum_nights_catg\",\n",
    "    \"host_listings_count_catg\",\n",
    "    \"bathrooms_catg\",\n",
    "    \"bedrooms_catg\",\n",
    "    \"beds_catg\",\n",
    "    \"security_deposit\",\n",
    "    \"extra_people\",\n",
    "]\n",
    "cont_cols = [\"latitude\", \"longitude\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ae90a5d",
   "metadata": {},
   "source": [
    "## Step 1: the preprocessors\n",
    "\n",
    "### Scenario 1: only the images do not fit in disk\n",
    "\n",
    "In this case we can prepare the data in the '_standard_' way"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d0579edf",
   "metadata": {},
   "outputs": [],
   "source": [
    "tab_preprocessor = TabPreprocessor(\n",
    "    embed_cols=cat_embed_cols,\n",
    "    continuous_cols=cont_cols,\n",
    "    default_embed_dim=8,\n",
    "    verbose=0,\n",
    ")\n",
    "\n",
    "text_preprocessor = TextPreprocessor(\n",
    "    text_col=text_col,\n",
    "    n_cpus=1,\n",
    ")\n",
    "\n",
    "img_preprocessor = ImagePreprocessor(\n",
    "    img_col=img_col,\n",
    "    img_path=img_path,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b3f56f59",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The vocabulary contains 2192 tokens\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "ImagePreprocessor(img_col=id, img_path=../tmp_data/airbnb/property_picture/, width=224, height=224, verbose=1)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tab_preprocessor.fit(airbnb_data)\n",
    "text_preprocessor.fit(airbnb_data)\n",
    "img_preprocessor.fit(airbnb_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a871266",
   "metadata": {},
   "source": [
    "### Scenario 2: the tabular data is also huge \n",
    "\n",
    "Then we need to prepare it in chunks. Note that, unfortunately, the tabular and text preprocessors need to see the whole dataset once. This is because to process tabular or text data we need to encode values. For those encodings to be consistent they need to have seen the whole dataset. Alternatively, one could code a solution with some streaming encoder for both datasets. However, such implementation is not trivial for this library (and in general). I also don't think that having to see the whole data once is such a big limitation. Let's see how is done.\n",
    "\n",
    "Note that I have not mentioned the image dataset. This is because the processing of the image dataset does not require any form of encoding and in consequence can be done 'on the fly'. Therefore, no `ChunkImgPreprocessor` processor is needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "585056f9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "chunk in loop: 1\n",
      "chunk in loop: 2\n",
      "chunk in loop: 3\n",
      "chunk in loop: 4\n",
      "chunk in loop: 5\n",
      "chunk in loop: 6\n",
      "chunk in loop: 7\n",
      "chunk in loop: 8\n"
     ]
    }
   ],
   "source": [
    "chunk_tab_preprocessor = ChunkTabPreprocessor(\n",
    "    embed_cols=cat_embed_cols,\n",
    "    continuous_cols=cont_cols,\n",
    "    n_chunks=n_chunks,\n",
    "    default_embed_dim=8,\n",
    "    verbose=0,\n",
    ")\n",
    "\n",
    "chunk_text_preprocessor = ChunkTextPreprocessor(\n",
    "    n_chunks=n_chunks,\n",
    "    text_col=text_col,\n",
    "    n_cpus=1,\n",
    "    verbose=0,\n",
    ")\n",
    "\n",
    "for i, chunk in enumerate(\n",
    "    pd.read_csv(\"/\".join([data_path, train_fname]), chunksize=chunksize)\n",
    "):\n",
    "    print(f\"chunk in loop: {i + 1}\")\n",
    "    chunk_tab_preprocessor.fit(chunk)\n",
    "    chunk_text_preprocessor.fit(chunk)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e8e0563",
   "metadata": {},
   "source": [
    "## Step 2: the `[...]FromFolder` classes\n",
    "\n",
    "Once we have the preprocessors, we need to instantiate the classes that will enable us to load the data from their respective folders. From now on I am going to proceed with the `chunk_tab_preprocessor`, `chunk_text_preprocessor` and `img_preprocessor`, but the code would be identical if instead of the first two preprocessors we decided to use the `tab_preprocessor` and `text_preprocessor`.\n",
    "\n",
    "Once more, our reference datasets are the tabular datasets, which we have splitted in train, eval and test prior to start the coding. Therefore, we will eventually need a loader for each split"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0abc2c3f",
   "metadata": {},
   "outputs": [],
   "source": [
    "train_tab_folder = TabFromFolder(\n",
    "    fname=train_fname,\n",
    "    directory=data_path,\n",
    "    target_col=target_col,\n",
    "    preprocessor=tab_preprocessor,\n",
    "    text_col=text_col,\n",
    "    img_col=img_col,\n",
    ")\n",
    "\n",
    "# Note how we can use the `train_tab_folder` as reference so we don't have to\n",
    "# define all parameters again\n",
    "eval_tab_folder = TabFromFolder(fname=eval_fname, reference=train_tab_folder)\n",
    "\n",
    "# Note that for the test set we can ignore the target as no metric will be\n",
    "# computed by the `predict` method\n",
    "test_tab_folder = TabFromFolder(\n",
    "    fname=test_fname, reference=train_tab_folder, ignore_target=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "50490084",
   "metadata": {},
   "outputs": [],
   "source": [
    "# for the text and image datasets we do not need to specify eval or test loaders\n",
    "text_folder = TextFromFolder(preprocessor=text_preprocessor)\n",
    "img_folder = ImageFromFolder(preprocessor=img_preprocessor)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "994c641a",
   "metadata": {},
   "source": [
    "## Step 3: pytorch datasets and dataloaders\n",
    "\n",
    "From here in advance, is all very 'standard' if you are familiar with pytorch. One needs to define a class that inherits from the `Dataset` class in pytorch. Then this will be passed to a `DataLoader` class and we are ready to train. Our `Dataset` child class is `WideDeepDatasetFromFolder`. This class will use the tabular dataset and the corresponding text and image columns to load the adequate data in the batches\n",
    "\n",
    "Let's do it"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "e1363873",
   "metadata": {},
   "outputs": [],
   "source": [
    "train_dataset_folder = WideDeepDatasetFromFolder(\n",
    "    n_samples=train_size,\n",
    "    tab_from_folder=train_tab_folder,\n",
    "    text_from_folder=text_folder,\n",
    "    img_from_folder=img_folder,\n",
    ")\n",
    "\n",
    "# Note that the eval and test loaders only need their corresponding\n",
    "# `TabFromFolder` classes. The rest of the parameters can be defined\n",
    "# via a `reference` `TabFromFolder` class\n",
    "eval_dataset_folder = WideDeepDatasetFromFolder(\n",
    "    n_samples=eval_size,\n",
    "    tab_from_folder=eval_tab_folder,\n",
    "    reference=train_dataset_folder,\n",
    ")\n",
    "\n",
    "test_dataset_folder = WideDeepDatasetFromFolder(\n",
    "    n_samples=test_size,\n",
    "    tab_from_folder=test_tab_folder,\n",
    "    reference=train_dataset_folder,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "34ffda86",
   "metadata": {},
   "outputs": [],
   "source": [
    "train_loader = DataLoader(train_dataset_folder, batch_size=16, num_workers=1)\n",
    "eval_loader = DataLoader(eval_dataset_folder, batch_size=16, num_workers=1)\n",
    "test_loader = DataLoader(test_dataset_folder, batch_size=16, num_workers=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e352a39a",
   "metadata": {},
   "source": [
    "And from here on is business as usual: \n",
    "\n",
    "## Step 4: define the model "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "0b330ffa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "WideDeep(\n",
       "  (deeptabular): Sequential(\n",
       "    (0): TabMlp(\n",
       "      (cat_embed): DiffSizeCatEmbeddings(\n",
       "        (embed_layers): ModuleDict(\n",
       "          (emb_layer_host_listings_count): Embedding(28, 10, padding_idx=0)\n",
       "          (emb_layer_neighbourhood_cleansed): Embedding(33, 11, padding_idx=0)\n",
       "          (emb_layer_is_location_exact): Embedding(3, 2, padding_idx=0)\n",
       "          (emb_layer_property_type): Embedding(4, 3, padding_idx=0)\n",
       "          (emb_layer_room_type): Embedding(4, 3, padding_idx=0)\n",
       "          (emb_layer_accommodates): Embedding(14, 7, padding_idx=0)\n",
       "          (emb_layer_bathrooms): Embedding(11, 6, padding_idx=0)\n",
       "          (emb_layer_bedrooms): Embedding(7, 4, padding_idx=0)\n",
       "          (emb_layer_beds): Embedding(11, 6, padding_idx=0)\n",
       "          (emb_layer_guests_included): Embedding(11, 6, padding_idx=0)\n",
       "          (emb_layer_minimum_nights): Embedding(25, 9, padding_idx=0)\n",
       "          (emb_layer_instant_bookable): Embedding(3, 2, padding_idx=0)\n",
       "          (emb_layer_cancellation_policy): Embedding(6, 4, padding_idx=0)\n",
       "          (emb_layer_has_house_rules): Embedding(3, 2, padding_idx=0)\n",
       "          (emb_layer_host_gender): Embedding(4, 3, padding_idx=0)\n",
       "          (emb_layer_accommodates_catg): Embedding(4, 3, padding_idx=0)\n",
       "          (emb_layer_guests_included_catg): Embedding(4, 3, padding_idx=0)\n",
       "          (emb_layer_minimum_nights_catg): Embedding(4, 3, padding_idx=0)\n",
       "          (emb_layer_host_listings_count_catg): Embedding(5, 3, padding_idx=0)\n",
       "          (emb_layer_bathrooms_catg): Embedding(4, 3, padding_idx=0)\n",
       "          (emb_layer_bedrooms_catg): Embedding(5, 3, padding_idx=0)\n",
       "          (emb_layer_beds_catg): Embedding(5, 3, padding_idx=0)\n",
       "          (emb_layer_security_deposit): Embedding(53, 15, padding_idx=0)\n",
       "          (emb_layer_extra_people): Embedding(39, 12, padding_idx=0)\n",
       "        )\n",
       "        (embedding_dropout): Dropout(p=0.0, inplace=False)\n",
       "      )\n",
       "      (cont_norm): Identity()\n",
       "      (encoder): MLP(\n",
       "        (mlp): Sequential(\n",
       "          (dense_layer_0): Sequential(\n",
       "            (0): Linear(in_features=128, out_features=32, bias=True)\n",
       "            (1): ReLU(inplace=True)\n",
       "            (2): Dropout(p=0.1, inplace=False)\n",
       "          )\n",
       "          (dense_layer_1): Sequential(\n",
       "            (0): Linear(in_features=32, out_features=16, bias=True)\n",
       "            (1): ReLU(inplace=True)\n",
       "            (2): Dropout(p=0.1, inplace=False)\n",
       "          )\n",
       "        )\n",
       "      )\n",
       "    )\n",
       "    (1): Linear(in_features=16, out_features=1, bias=True)\n",
       "  )\n",
       "  (deeptext): Sequential(\n",
       "    (0): BasicRNN(\n",
       "      (word_embed): Embedding(2192, 32, padding_idx=1)\n",
       "      (rnn): LSTM(32, 64, num_layers=2, batch_first=True, dropout=0.1)\n",
       "      (rnn_mlp): Identity()\n",
       "    )\n",
       "    (1): Linear(in_features=64, out_features=1, bias=True)\n",
       "  )\n",
       "  (deepimage): Sequential(\n",
       "    (0): Vision(\n",
       "      (features): Sequential(\n",
       "        (conv_layer_0): Sequential(\n",
       "          (0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n",
       "          (1): BatchNorm2d(64, eps=1e-05, momentum=0.01, affine=True, track_running_stats=True)\n",
       "          (2): LeakyReLU(negative_slope=0.1, inplace=True)\n",
       "          (maxpool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "        )\n",
       "        (conv_layer_1): Sequential(\n",
       "          (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "          (1): BatchNorm2d(128, eps=1e-05, momentum=0.01, affine=True, track_running_stats=True)\n",
       "          (2): LeakyReLU(negative_slope=0.1, inplace=True)\n",
       "        )\n",
       "        (conv_layer_2): Sequential(\n",
       "          (0): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "          (1): BatchNorm2d(256, eps=1e-05, momentum=0.01, affine=True, track_running_stats=True)\n",
       "          (2): LeakyReLU(negative_slope=0.1, inplace=True)\n",
       "        )\n",
       "        (conv_layer_3): Sequential(\n",
       "          (0): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "          (1): BatchNorm2d(512, eps=1e-05, momentum=0.01, affine=True, track_running_stats=True)\n",
       "          (2): LeakyReLU(negative_slope=0.1, inplace=True)\n",
       "          (adaptiveavgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n",
       "        )\n",
       "      )\n",
       "    )\n",
       "    (1): Linear(in_features=512, out_features=1, bias=True)\n",
       "  )\n",
       ")"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# for example\n",
    "basic_rnn = BasicRNN(\n",
    "    vocab_size=len(text_preprocessor.vocab.itos),\n",
    "    embed_dim=32,\n",
    "    hidden_dim=64,\n",
    "    n_layers=2,\n",
    ")\n",
    "\n",
    "deepimage = Vision()\n",
    "\n",
    "deepdense = TabMlp(\n",
    "    mlp_hidden_dims=[32, 16],\n",
    "    column_idx=tab_preprocessor.column_idx,\n",
    "    cat_embed_input=tab_preprocessor.cat_embed_input,\n",
    "    continuous_cols=cont_cols,\n",
    ")\n",
    "\n",
    "model = WideDeep(\n",
    "    deeptabular=deepdense,\n",
    "    deeptext=basic_rnn,\n",
    "    deepimage=deepimage,\n",
    ")\n",
    "\n",
    "model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b57c52c",
   "metadata": {},
   "source": [
    "## Step 5: fit and predict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "57d6021b",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch 1: 100%|███████████████████████████████████████████████████████████████████████████████████| 50/50 [03:41<00:00,  4.42s/it, loss=1.64e+4]\n",
      "valid: 100%|███████████████████████████████████████████████████████████████████████████████████████| 7/7 [00:23<00:00,  3.30s/it, loss=6.27e+3]\n"
     ]
    }
   ],
   "source": [
    "trainer = TrainerFromFolder(\n",
    "    model,\n",
    "    objective=\"regression\",\n",
    ")\n",
    "\n",
    "trainer.fit(\n",
    "    train_loader=train_loader,\n",
    "    eval_loader=eval_loader,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "0a63c8df",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "predict: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████| 7/7 [00:22<00:00,  3.26s/it]\n"
     ]
    }
   ],
   "source": [
    "preds = trainer.predict(test_loader=test_loader)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c91c2818",
   "metadata": {},
   "source": [
    "Note that in the case of predict you could also choose to do this"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "bf8ac604",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_test = pd.read_csv(\"/\".join([data_path, test_fname]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "c27de38f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reading Images from ../tmp_data/airbnb/property_picture/\n",
      "Resizing\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 101/101 [00:00<00:00, 708.23it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Computing normalisation metrics\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# if the images for the test set fit in memory\n",
    "X_tab_test = chunk_tab_preprocessor.transform(df_test)\n",
    "X_text_test = chunk_text_preprocessor.transform(df_test)\n",
    "X_img_test = img_preprocessor.transform(df_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "5a4e98ef",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "predict: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:03<00:00,  1.14it/s]\n"
     ]
    }
   ],
   "source": [
    "preds = trainer.predict(\n",
    "    X_tab=X_tab_test, X_text=X_text_test, X_img=X_img_test, batch_size=32\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
